package environment.dungeons;

import static org.junit.Assert.*;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.junit.*;

import environment.Direction;
import environment.Position;


public class CompositeDungeonTest {
	
	static CompositeDungeon<Square> compositeDungeon;
	static ElementaryDungeon<Square> elem1;
	static ElementaryDungeon<TeleportableSquare> elem2;
	
	static CompositeDungeon<Square> sub1;
	static CompositeDungeon<TeleportableSquare> sub2;
	
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		compositeDungeon = new CompositeDungeon<Square>(20, 10, 5);
		
		elem1 = new Level<Square>();
		elem2 = new Shaft<TeleportableSquare>(Direction.NORTH);
		
		sub1 = new CompositeDungeon<Square>(10, 5, 5);
		sub2 = new CompositeDungeon<TeleportableSquare>(4, 10, 5);
	}
	
	@Test
	public void testConstruct(){
		assertEquals(compositeDungeon.getMaximumX(), 20);
		assertEquals(compositeDungeon.getMaximumY(), 10);
		assertEquals(compositeDungeon.getMaximumZ(), 5);
	}
	
//Elementary SubDungeons
	//ADDING
	@Test
	public void testAddingElementarySubDungeons(){
		assertFalse(compositeDungeon.hasAsSubDungeon(elem1));
		compositeDungeon.addSubDungeonAt(elem1, 10, 0, 0);
		assertEquals(compositeDungeon.getNbSubDungeons(), 1);
		assertTrue(compositeDungeon.hasAsSubDungeon(elem1));
		assertFalse(compositeDungeon.hasAsSubDungeon(elem2));
		compositeDungeon.addSubDungeonAt(elem2, 3, 0, 1);
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertTrue(compositeDungeon.hasAsSubDungeon(elem2));
	}
	@Test
	public void testGetElementarySubDungeonAt(){
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(10, 0, 0)), elem1);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(15, 4, 0)), elem1);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(3, 0, 1)), elem2);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(3, 10, 1)), elem2);
	}
	@Test
	public void testGetOriginOfElementarySubDungeon(){
		assertEquals(compositeDungeon.getOriginOfSubDungeon(elem1), new Position(10, 0, 0));
		assertEquals(compositeDungeon.getOriginOfSubDungeon(elem2), new Position(3, 0, 1));
	}
	@Test
	public void testHasAsIndirectElementarySubDungeon(){
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(elem1));
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(elem2));
	}
	@Test
	public void testGetAllSubDungeonsElementary(){
		Map<Position,Dungeon<? extends Square>> map = compositeDungeon.getSubDungeons();
		assertTrue(map.size() == 2);
		assertEquals(map.get(compositeDungeon.getOriginOfSubDungeon(elem1)), elem1);
		assertEquals(map.get(compositeDungeon.getOriginOfSubDungeon(elem2)), elem2);
	}
	@Test
	public void testGetDirectSubDungeonsElementary(){
		Set<Dungeon<? extends Square>> set = compositeDungeon.getSetSubDungeons();
		assertTrue(set.size() == 2);
		assertTrue(set.contains(elem1));
		assertTrue(set.contains(elem2));
		for(Dungeon<? extends Square> dung: compositeDungeon.getSetSubDungeons())
			assertTrue(set.contains(dung));
	}
	@Test
	public void testGetElementarySubDungeonsElementary(){
		Set<ElementaryDungeon<? extends Square>> set = compositeDungeon.getElementarySubDungeons();
		assertTrue(set.size() == 2);
		assertTrue(set.contains(elem1));
		assertTrue(set.contains(elem2));
		for(Dungeon<? extends Square> dung: compositeDungeon.getSetSubDungeons())
			if(dung.getClass().isAssignableFrom(ElementaryDungeon.class))
				assertTrue(set.contains(dung));
	}
	
	//REMOVING
	@Test
	public void testRemovingElementarySubDungeon(){
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertTrue(compositeDungeon.hasAsSubDungeon(elem1));
		compositeDungeon.removeAsSubDungeon(elem1);
		assertEquals(compositeDungeon.getNbSubDungeons(), 1);
		assertFalse(compositeDungeon.hasAsSubDungeon(elem1));
		assertFalse(elem1.isTerminated());
		
		assertTrue(compositeDungeon.hasAsSubDungeon(elem2));
		compositeDungeon.removeSubDungeonAt(new Position(3, 0, 1));
		assertEquals(compositeDungeon.getNbSubDungeons(), 0);
		assertFalse(compositeDungeon.hasAsSubDungeon(elem2));
		assertFalse(elem2.isTerminated());
	}
	
//Composite SubDungeons
	@Test
	public void testAddingCompositeSubDungeons(){
		assertFalse(compositeDungeon.hasAsSubDungeon(sub1));
		compositeDungeon.addSubDungeonAt(sub1, 10, 5, 0);
		assertEquals(compositeDungeon.getNbSubDungeons(), 1);
		assertTrue(compositeDungeon.hasAsSubDungeon(sub1));
		assertFalse(compositeDungeon.hasAsSubDungeon(sub2));
		compositeDungeon.addSubDungeonAt(sub2, 5, 0, 0);
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertTrue(compositeDungeon.hasAsSubDungeon(sub2));
	}
	@Test
	public void testGetCompositeSubDungeonAt(){
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(10, 5, 0)), sub1);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(20, 8, 4)), sub1);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(5, 0, 0)), sub2);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(8, 10, 3)), sub2);
	}
	@Test
	public void testGetOriginOfCompositeSubDungeon(){
		assertEquals(compositeDungeon.getOriginOfSubDungeon(sub1), new Position(10, 5, 0));
		assertEquals(compositeDungeon.getOriginOfSubDungeon(sub2), new Position(5, 0, 0));
	}
	@Test
	public void testHasAsIndirectCompositeSubDungeon(){
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(sub1));
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(sub2));
	}
	@Test
	public void testGetAllSubDungeonsComposite(){
		Map<Position,Dungeon<? extends Square>> map = compositeDungeon.getSubDungeons();
		assertTrue(map.size() == 2);
		assertEquals(map.get(compositeDungeon.getOriginOfSubDungeon(sub1)), sub1);
		assertEquals(map.get(compositeDungeon.getOriginOfSubDungeon(sub2)), sub2);
	}
	@Test
	public void testGetDirectSubDungeonsComposite(){
		Set<Dungeon<? extends Square>> set = compositeDungeon.getSetSubDungeons();
		assertTrue(set.size() == 2);
		assertTrue(set.contains(sub1));
		assertTrue(set.contains(sub2));
		for(Dungeon<? extends Square> dung: compositeDungeon.getSetSubDungeons())
			assertTrue(set.contains(dung));
	}
	@Test
	public void testGetElementarySubDungeonsComposite(){
		assertTrue(compositeDungeon.getElementarySubDungeons().size() == 0);
	}
	
	//REMOVING
	@Test
	public void testRemovingCompositeSubDungeon(){
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertTrue(compositeDungeon.hasAsSubDungeon(sub1));
		compositeDungeon.removeAsSubDungeon(sub1);
		assertEquals(compositeDungeon.getNbSubDungeons(), 1);
		assertFalse(compositeDungeon.hasAsSubDungeon(sub1));
		assertFalse(sub1.isTerminated());
		
		assertTrue(compositeDungeon.hasAsSubDungeon(sub2));
		compositeDungeon.removeSubDungeonAt(new Position(9, 10, 3));
		assertEquals(compositeDungeon.getNbSubDungeons(), 0);
		assertFalse(compositeDungeon.hasAsSubDungeon(sub2));
		assertFalse(sub2.isTerminated());
	}
	
//Complex structures	
	static CompositeDungeon<Square> subsub1;
	static ElementaryDungeon<Square> element1;
	static ElementaryDungeon<TeleportableSquare> element2;
	@Test
	public void testCreatingComplexDungeon(){
		assertEquals(compositeDungeon.getSubDungeons().size(), 0);
		
		subsub1 = new CompositeDungeon<Square>(2, 2, 2);
		element1 = new Level<Square>(2,2);
		element2 = new Shaft<TeleportableSquare>(3, Direction.EAST);
		
		compositeDungeon.addSubDungeonAt(sub1, 0, 5, 0);
		assertEquals(compositeDungeon.getNbSubDungeons(), 1);
		assertTrue(compositeDungeon.hasAsSubDungeon(sub1));
		try {
			compositeDungeon.addSubDungeonAt(sub2, 10, 0, 0);
			assert false;
		} catch (IllegalArgumentException e) {
			assert true;
		}
		assertEquals(compositeDungeon.getNbSubDungeons(), 1);
		assertFalse(compositeDungeon.hasAsSubDungeon(sub2));
		compositeDungeon.addSubDungeonAt(sub2, 11, 0, 0);
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertTrue(compositeDungeon.hasAsSubDungeon(sub2));
		sub1.addSubDungeonAt(subsub1, 1,1,2);
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(subsub1));
		try {
			sub1.addSubDungeonAt(element1, 1, 1, 2);
			assert false;
		} catch (IllegalArgumentException e) {
			assert true;
		}
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertFalse(compositeDungeon.hasAsSubDungeon(element1));
		subsub1.addSubDungeonAt(element1, 0, 0, 1);
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(element1));
		sub2.addSubDungeonAt(element2, 1, 5, 3);
		assertEquals(compositeDungeon.getNbSubDungeons(), 2);
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(element2));
	}
	@Test
	public void testComplexGetSubDungeonAt(){
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(0, 5, 0)), sub1);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(7, 8, 5)), sub1);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(11, 0, 0)), sub2);
		assertEquals(compositeDungeon.getSubDungeonAt(new Position(15, 2, 3)), sub2);
	}
	@Test
	public void testComplexGetOriginOfSubDungeon(){
		assertEquals(compositeDungeon.getOriginOfSubDungeon(sub1), new Position(0, 5, 0));
		assertEquals(compositeDungeon.getOriginOfSubDungeon(sub2), new Position(11, 0, 0));
	}
	@Test
	public void testComplexHasAsIndirectSubDungeon(){
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(element1));
		assertTrue(compositeDungeon.hasAsIndirectSubDungeon(element2));
	}
	@Test
	public void testComplexGetAllSubDungeons(){
		Map<Position,Dungeon<? extends Square>> map = compositeDungeon.getSubDungeons();
		assertTrue(map.size() == 2);
		assertEquals(map.get(compositeDungeon.getOriginOfSubDungeon(sub1)), sub1);
		assertEquals(map.get(compositeDungeon.getOriginOfSubDungeon(sub2)), sub2);
	}
	@Test
	public void testComplexGetDirectSubDungeons(){
		Set<Dungeon<? extends Square>> set = compositeDungeon.getSetSubDungeons();
		assertTrue(set.size() == 2);
		assertTrue(set.contains(sub1));
		assertTrue(set.contains(sub2));
		for(Dungeon<? extends Square> dung: compositeDungeon.getSetSubDungeons())
			assertTrue(set.contains(dung));
	}
	@Test
	public void testComplexGetElementarySubDungeons(){
		Set<ElementaryDungeon<? extends Square>> set = compositeDungeon.getElementarySubDungeons();
		assertTrue(set.size() == 2);
		assertTrue(set.contains(element1));
		assertTrue(set.contains(element2));
		for(Dungeon<? extends Square> dung: compositeDungeon.getSetSubDungeons())
			if(dung.getClass().isAssignableFrom(ElementaryDungeon.class))
				assertTrue(set.contains(dung));
	}
	
	static Square square1;
	static Square square2;
	static Square square3;
	static Dungeon<Square> element3;
	
//Squares
	//Adding
	@Test
	public void testAddingSquares(){
		//Setup:
		element3 = new Level<Square>(5,5);
		sub1.addSubDungeonAt(element3, 0, 0, 1);
		square1 = new NormalSquare();
		square2 = new TransparentSquare(Direction.EAST);
		square3 = new Rock();
		
		compositeDungeon.addSquareAt(square1, new Position(3,6,1));
		assertTrue(compositeDungeon.hasAsSquare(square1));
		assertTrue(sub1.hasAsSquare(square1));
		assertTrue(element3.hasAsSquare(square1));
		assertTrue(square1.isInUse());
		assertEquals(element3.getSquareAt(new Position(3, 1, 0)), square1);
		
		compositeDungeon.addSquareAt(square2, new Position(4,6,1));
		assertTrue(compositeDungeon.hasAsSquare(square2));
		assertTrue(sub1.hasAsSquare(square2));
		assertTrue(element3.hasAsSquare(square2));
		assertTrue(square2.isInUse());
		assertEquals(element3.getSquareAt(new Position(4, 1, 0)), square2);
		assertTrue(square1.isOpenNeighbourOf(square2));
		assertEquals(square1.getBorderAt(Direction.EAST),square2.getBorderAt(Direction.WEST));
		
		compositeDungeon.addSquareAt(square3, new Position(3, 6, 3));
		assertTrue(compositeDungeon.hasAsSquare(square3));
		assertTrue(sub1.hasAsSquare(square3));
		assertTrue(subsub1.hasAsSquare(square3));
		assertTrue(element1.hasAsSquare(square3));
		assertTrue(square3.isInUse());
		assertEquals(element1.getSquareAt(new Position(2, 0, 0)), square3);		
	}
	//iterate
	@Test
	public void testIterator(){
		Iterator<Square> it = compositeDungeon.iterator();
		Set<Square> squares = new HashSet<Square>();
		while(it.hasNext())
			squares.add(it.next());
		assertTrue(squares.size()==3);
	}
	//Removing
	@Test
	public void testRemovingSquares(){
		compositeDungeon.removeAsSquare(square3);
		assertTrue(square3.isTerminated());
		assertFalse(compositeDungeon.hasAsSquare(square3));
		assertFalse(sub1.hasAsSquare(square3));
		assertFalse(subsub1.hasAsSquare(square3));
		assertFalse(element1.hasAsSquare(square3));
		assertEquals(element1.getSquareAt(new Position(2, 0, 0)), null);
		
		compositeDungeon.removeSquareAt(new Position(4,6,1));
		assertTrue(square2.isTerminated());
		assertFalse(compositeDungeon.hasAsSquare(square2));
		assertFalse(sub1.hasAsSquare(square2));
		assertFalse(element3.hasAsSquare(square2));
		assertEquals(element3.getSquareAt(new Position(4, 1, 0)), null);
		assertFalse(square1.isOpenNeighbourOf(square2));
		assertEquals(null,square2.getBorderAt(Direction.WEST));
		
		sub1.removeAsSquare(square1);
		assertTrue(square1.isTerminated());
		assertFalse(compositeDungeon.hasAsSquare(square1));
		assertFalse(sub1.hasAsSquare(square1));
		assertFalse(element3.hasAsSquare(square1));
		assertEquals(element3.getSquareAt(new Position(3, 1, 0)), null);
	}
//Terminate parentDungeon
	@Test
	public void testTerminateParent(){
		square1 = new NormalSquare();
		square2 = new TransparentSquare(Direction.EAST);
		square3 = new Rock();
		compositeDungeon.addSquareAt(square1, new Position(3,6,1));
		compositeDungeon.addSquareAt(square2, new Position(4,6,1));
		compositeDungeon.addSquareAt(square3, new Position(3, 6, 3));
		compositeDungeon.terminate();
		assertTrue(compositeDungeon.isTerminated());
		assertTrue(compositeDungeon.getSetSubDungeons().size() == 0);
		assertTrue(compositeDungeon.getSquares().size() == 0);
		assertTrue(sub1.isTerminated());
		assertTrue(sub1.getSetSubDungeons().size() == 0);
		assertTrue(sub1.getSquares().size() == 0);
		assertTrue(sub2.isTerminated());
		assertTrue(sub2.getSetSubDungeons().size() == 0);
		assertTrue(sub2.getSquares().size() == 0);
		assertTrue(subsub1.isTerminated());
		assertTrue(subsub1.getSetSubDungeons().size() == 0);
		assertTrue(subsub1.getSquares().size() == 0);
		assertTrue(element1.isTerminated());
		assertTrue(element1.getSquares().size() == 0);
		assertTrue(element2.isTerminated());
		assertTrue(element2.getSquares().size() == 0);
		assertTrue(element3.isTerminated());
		assertTrue(element3.getSquares().size() == 0);
		assertTrue(square1.isTerminated());
		assertTrue(square2.isTerminated());
		assertTrue(square3.isTerminated());
	}
}
